<?php


namespace App\Helpers;


use App\Enums\Sex;
use Carbon\Carbon;

class Identity {
    /**
     * 中国大陆身份证号码
     *
     * @var string
     */
    private ?string $number = null;
    /**
     * 中国大陆身份证号码长度
     *
     * @var int
     */
    private ?int $length = null;

    private bool $isValidate = false;
    /**
     * 加权因子
     *
     * @var array
     */
    private $factor = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];

    /**
     * 校验码
     *
     * @var array
     */
    private $verifyCode = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'];

    /**
     * Identity constructor.
     *
     * @param  $id string 身份证号码
     */
    public function __construct($id) {
        $this->number = strtoupper(trim($id));
        $this->length = strlen($this->number);
        $this->isValidate();
    }

    /**
     * @return bool
     */
    public function validate(): bool {
        return $this->isValidate;
    }

    /**
     * @return string
     */
    public function getNumber(): ?string {
        return $this->number;
    }

    /**
     * @return int
     */
    public function getLength(): ?int {
        return $this->length;
    }

    /**
     * @return int|null
     */
    public function getAge(): ?int {
        if ($this->isValidate) {
            return $this->age = Carbon::parse($this->getBirth())->diffInYears();
        }
        return null;
    }

    /**
     * 根据身份证信息获取其性别
     */
    public function getGender(): ?int {
        if (!$this->isValidate) {
            return null;
        }
        // 倒数第2位
        $gender = substr($this->number, 16, 1);

        return ($gender % 2 == 0) ? Sex::WOMEN : Sex::MAN;;
    }

    /**
     * 根据身份证信息获取其生日信息
     *
     * @param string $delimiter 生日日期分割符
     *
     * @return bool|string
     */
    public function getBirth() {
        if (!$this->isValidate) {
            return null;
        }

        $birth = [
            'year' => substr($this->number, 6, 4),
            'month' => substr($this->number, 10, 2),
            'day' => substr($this->number, 12, 2),
        ];
        return implode('-', $birth);
    }

    /**
     * 通过正则表达式检测身份证号码格式
     *
     * @return bool
     */
    protected function checkFormat() {
        $this->id15To18();
        if ($this->length == 15) {
            $pattern = '/^\d{6}(18|19|20)\d{2}(0[1-9]|1[012])(0[1-9]|[12]\d|3[01])\d{3}$/';
        } else {
            $pattern = '/^\d{6}(18|19|20)\d{2}(0[1-9]|1[012])(0[1-9]|[12]\d|3[01])\d{3}(\d|X)$/';
        }

        if (preg_match($pattern, $this->number)) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * 检测身份证生日是否正确
     *
     * @return bool
     */
    protected function checkBirth() {
        $year = substr($this->number, 6, 4);
        $month = substr($this->number, 10, 2);
        $day = substr($this->number, 12, 2);

        if (checkdate($month, $day, $year)) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * 校验身份证号码最后一位校验码
     *
     * @return bool
     */
    protected function checkLastCode() {
        if ($this->length == 15) {
            return true;
        }
        $sum = 0;
        for ($i = 0; $i < 17; $i++) {
            $sum += substr($this->number, $i, 1) * $this->factor[$i];
        }

        $mod = $sum % 11;

        if ($this->verifyCode[$mod] != substr($this->number, -1)) {
            return false;
        }
        return true;
    }

    /**
     * 将 15 位身份证转化为 18 位身份证号码
     *
     * @return string
     */
    protected function id15To18() {
        if ($this->length == 15) {
            // 如果身份证顺序码是996 997 998 999，这些是为百岁以上老人的特殊编码
            if (array_search(substr($this->number, 12, 3), ['996', '997', '998', '999']) !== false) {
                $this->number = substr($this->number, 0, 6) . '18' . substr($this->number, 6, 9);
            } else {
                $this->number = substr($this->number, 0, 6) . '19' . substr($this->number, 6, 9);
            }
            // 补全最后一位
        }
    }

    /**
     * 验证身份证号码是否正确
     *
     * @return bool
     */
    private function isValidate() {
        if ($this->checkFormat() && $this->checkBirth() && $this->checkLastCode()) {
            $this->isValidate = true;
        }
    }

    private function CardId15To18($card) {
        $len = strlen($card);
        if ($len == 18) {
            return $card;
        } else if ($len != 15) {
            return false;
        }
        $result = [];
        for ($i = 0; $i < $len; $i++) {
            if ($i <= 5) {
                $result[$i] = intval($card[$i]);
            } else {
                //15位的年份是两位数，18位的是4位数，留出2位
                $result[$i + 2] = intval($card[$i]);
            }
        }
        //留出的2位，补充为年份，年份最后两位小于17,年份为20XX，否则为19XX
        if (intval(substr($card, 6, 2)) <= 17) {
            $result[6] = 2;
            $result[7] = 0;
        } else {
            $result[6] = 1;
            $result[7] = 9;
        }
        ksort($result);
        //计算最后一位
        //前十七位乘以系数[7,9,10,5,8,4,2,1,6,3,7,9,10,5,8,4,2],
        $arrInt = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
        $sum = 0;
        for ($i = 0; $i < 17; $i++) {
            $sum += $result[$i] * $arrInt[$i];
        }
        //对11求余，的余数 0 - 10
        $rod = $sum % 11;
        //所得余数映射到对应数字即可
        $arrCh = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'];
        $result[17] = $arrCh[$rod];
        return implode('', $result);
    }
}
